项目设置settings.py增改设置
import os import sys from django.urls import reverse_lazy # 添加App的路径 sys.path.insert(0, os.path.join(BASE_DIR, 'app_event')) # app移动到app_event添加 sys.path.insert(0, os.path.join(BASE_DIR, 'app_wiki')) # 添加wiki的路径 INSTALLED_APPS = [ 'account', 'mptt', # pip install django-mptt 'hitcount', # pip install django-hitcount 点击数 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'event', 'multiple_filter', 'wiki', ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 设置可放置模板的路径 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.media', # 前端使用{{ MEDIA_URL }} ], }, }, ] # LANGUAGE_CODE = 'en-us' # 语言修改为中文 LANGUAGE_CODE = 'zh-hans' # TIME_ZONE = 'UTC' # 时区修改为重庆 TIME_ZONE = 'Asia/Chongqing' USE_I18N = True # USE_L10N = True USE_L10N = False # 设置时间显示格式,和前端js选择的格式相同,方便提交表单 DATE_FORMAT = 'Y-m-d' DATETIME_FORMAT = 'Y-m-d H:i:s' USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ] MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') PAGE_NUM = 5 # 每页显示5条 LOGIN_REDIRECT_URL = reverse_lazy('event:list') # LOGIN_URL = reverse_lazy('login') LOGIN_URL = reverse_lazy('account:login') LOGOUT_URL = reverse_lazy('account:logout') # 分页设置 PAGINATION_SETTINGS = { 'PAGE_RANGE_DISPLAYED': 5, 'MARGIN_PAGES_DISPLAYED': 2, 'SHOW_FIRST_PAGE_WHEN_INVALID': True, } # 使用这个参数,Celery就会忽略全部调度机制,立即调用代码 # increase_views.delay(wiki_content.id)和increase_views(wiki_content.id)语句效果一样,不需要加delay运行 CELERY_ALWAYS_EAGER = True # 是否发送邮件配置,不然测试的时候,每次用户一回复就会收到邮件 SEND_MAIL_CONFIG = False
static目录设置有点乱!
用户创建一个事件,然后进行处理,并提交事件的处理过程,处理完成后提交事件分析,最终确认时间处理完成。
模型设计部分代码
# 事件内容 class EventContent(models.Model): STATUS_CHOICES = ( (1, '未处理'), (2, '处理中'), (3, '完成'), (4, '提交分析'), (5, '确认完成'), (6, '已关闭') ) title = models.CharField(max_length=50, verbose_name='事件标题') content = models.TextField(verbose_name='事件正文') image = models.ImageField(upload_to='images/event/%Y/%m', blank=True, null=True, verbose_name='描述图片') created = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated = models.DateTimeField(auto_now=True, verbose_name='更新时间') status = models.IntegerField(choices=STATUS_CHOICES, default=1, verbose_name='事件状态') project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name='event_content', verbose_name='项目分类') category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='event_content', verbose_name='事件分类') level = models.ForeignKey(Level, on_delete=models.SET_NULL, null=True, blank=True, related_name='event_content', verbose_name='事件级别') user = models.ForeignKey(User, related_name='event_content', on_delete=models.CASCADE, verbose_name='创建人') start_time = models.DateTimeField(default=timezone.now, verbose_name='事件开始时间') end_time = models.DateTimeField(default=timezone.now, verbose_name='事件结束时间') pause_time = models.DateTimeField(default=timezone.now, verbose_name='事件暂停时间') class Meta: ordering = ['-created'] verbose_name = '事件内容' verbose_name_plural = verbose_name def time_interval(self): time_diff = self.end_time - timezone.now() days = time_diff.days seconds = time_diff.seconds minutes = seconds // 60 # 得到这些秒换算的分钟整数 second = seconds % 60 # 得到除去分钟后剩余的秒数 hours = minutes // 60 minute = minutes % 60 if self.status == 6: return '事件已关闭!' if days <= -1: return '处理已超时!' return '{}天{}时{}分'.format(days, hours, minute) def __str__(self): return self.title def get_content_as_markdown(self): """ 当使用Mardown功能时,我们需要先让它转义一下特殊字符,然后再解析出Markdown标签。 这样做之后,输出字符串可以安全的在模板中使用。 :return: """ return mark_safe(markdown(self.content, safe_mode='escape'))
url(r'^create/$', CreateEvent.as_view(), name='event_create'),
class EventContentForm(forms.ModelForm): class Meta: model = EventContent fields = ('title', 'content', 'image', 'project', 'category', 'level', 'end_time')
CreateEvent(View)类视图创建事件内容事件创建后跳转到事件列表页面
步骤:
url(r'^$', event, name='list'), url(r'^event-(?P<user_id>\d+)-(?P<status_id>\d+)-(?P<level_id>\d+)-(?P<category_id>\d+)-(?P<project_id>\d+).html$', event, name='event_filter'),
event(request, **kwargs)视图中拖过判断kwargs是否有值来选择处理的逻辑kwargs没有值,则显示【事件列表】的url,在这个页面也有通过表单POST筛选,但是不太好用,最终的查询结果都进行分页from pure_pagination import Paginator as pure_Paginator # 部分代码 # 使用pure_pagination try: page = request.GET.get('page', 1) except pure_PageNotAnInteger: page = 1 # 这里指从events中取5个出来,每页显示5个,为了测试,可这儿只显示1个 p = pure_Paginator(queryset, 10, request=request) events = p.page(page)
kwargs有值,则按照第二种方式进行筛选,这儿需要用到自定义模板标签,也就是后面的筛选事件接入,如下模板
{% load active %} <div class="card-body d-flex p-0"> <h3 class="card-title p-3">用户</h3> <ul class="nav nav-pills p-2"> {% active_all request.path 1 %} {% for user_item in user_list %} {% active request.path user_item 1 %} {% endfor %} </ul> </div> <div class="card-body d-flex p-0"> <h3 class="card-title p-3">状态</h3> <ul class="nav nav-pills p-2"> {% active_all request.path 2 %} {% for status_item in status_list %} {% active request.path status_item 2 %} {% endfor %} </ul> </div> <div class="card-body d-flex p-0"> <h3 class="card-title p-3">级别</h3> <ul class="nav nav-pills p-2"> {% active_all request.path 3 %} {% for level_item in level_list %} {% active request.path level_item 3 %} {% endfor %} </ul> </div> <div class="card-body d-flex p-0"> <h3 class="card-title p-3">分类</h3> <ul class="nav nav-pills p-2"> {% active_all request.path 4 %} {% for category_item in category_list %} {% active request.path category_item 4 %} {% endfor %} </ul> </div> <div class="card-body d-flex p-0"> <h3 class="card-title p-3">项目</h3> <ul class="nav nav-pills p-2"> {% active_all request.path 5 %} {% for project_item in project_list %} {% active request.path project_item 5 %} {% endfor %} </ul> </div>
视图部分代码如下
""" 多条件事件筛选 event-(?P<user_id>\d+)-(?P<status_id>\d+)-(?P<level_id>\d+)-(?P<category_id>\d+)-(?P<project_id>\d+).html {'user_id': '0', 'status_id': '0', 'level_id': '0', 'category_id': '0', 'project_id': '0'} """ filter_dict = dict() request_path = request.path # print('请求地址:', request_path) if kwargs['user_id'] != '0': filter_dict['user'] = get_object_or_404(User, id=kwargs['user_id']) if kwargs['status_id'] != '0': filter_dict['status'] = kwargs['status_id'] if kwargs['level_id'] != '0': filter_dict['level'] = get_object_or_404(Level, id=kwargs['level_id']) if kwargs['category_id'] != '0': filter_dict['category'] = get_object_or_404(Category, id=kwargs['category_id']) if kwargs['project_id'] != '0': filter_dict['project'] = get_object_or_404(Project, id=kwargs['project_id']) user_list = User.objects.all().values('id', 'username') # print(user_list) # 将事件状态转换为字典列表形式 status_list = list(map(lambda x: {'id': x[0], 'status_tag': x[1]}, EventContent.STATUS_CHOICES)) # print(status_list) level_list = Level.objects.all().values('id', 'level_tag') category_list = Category.objects.all().values('id', 'category_name') project_list = Project.objects.all().values('id', 'project_name') url_id_list = kwargs.values() # url中所有id:[0, 0, 0, 0, 0 ] visit_url = reverse('event:event_filter', args=url_id_list) event_url_list = get_group_url_list(visit_url) queryset = EventContent.objects.filter(**filter_dict) page = request.GET.get('page', 1) paginator = Paginator(queryset, settings.PAGE_NUM) # paginator是分页对象 try: events = paginator.page(page) except PageNotAnInteger: events = paginator.page(1) except EmptyPage: events = paginator.page(paginator.num_pages) return render(request, 'event.html', { 'events': events, 'EVENT_MENU_GROUP': event_url_list, 'visit_url': visit_url, # 用于筛选事件active 'user_list': user_list, 'status_list': status_list, 'level_list': level_list, 'category_list': category_list, 'project_list': project_list, })
模版标签,这个文件名字是activa.py,所以在模板中需要使用load导入
from django.utils.safestring import mark_safe from django import template register = template.Library() @register.simple_tag def active_all(request_path, index): url_part_list = request_path.split('-') # print(url_part_list) # ['/event', '0', '0', '0', '0', '0.html'] # 第五组带.html,需要分开判断 if url_part_list[index] == '0' or url_part_list[index] == '0.html': temp = ''' <li class="nav-item"> <a class="nav-link active" href="{href}">全部</a> </li> ''' else: temp = ''' <li class="nav-item"> <a class="nav-link" href="{href}">全部</a> </li> ''' if index != 5: url_part_list[index] = '0' else: url_part_list[index] = '0.html' href = '-'.join(url_part_list) return mark_safe(temp.format(href=href)) @register.simple_tag def active(request_path, item, index): url_part_list = request_path.split('-') # 下面判断中,前面表示 event-0-1-5-1-,后面表示 3.html if url_part_list[index] == str(item['id']) or url_part_list[index] == str(item['id']) + '.html': temp = ''' <li class="nav-item"> <a href="{href}" class="nav-link active">{name}</a> </li> ''' else: temp = ''' <li class="nav-item"> <a href="{href}" class="nav-link">{name}</a> </li> ''' if index == 5: # 第五组有后缀.html,需单独处理 url_part_list[index] = str(item['id']) + '.html' else: url_part_list[index] = str(item['id']) href = '-'.join(url_part_list) if index == 1: """ event-1-0-0-0-0.html event-2-0-0-0-0.html event-3-0-0-0-0.html """ return mark_safe(temp.format(href=href, name=item['username'])) if index == 2: return mark_safe(temp.format(href=href, name=item['status_tag'])) if index == 3: return mark_safe(temp.format(href=href, name=item['level_tag'])) if index == 4: return mark_safe(temp.format(href=href, name=item['category_name'])) if index == 5: return mark_safe(temp.format(href=href, name=item['project_name']))
用户未登录会提示登录
之后点击登录,会跳转到登录窗口,此时的链接是 http://127.0.0.1:8000/account/login/?next=/event/id/23/
处理步骤:
from .views import verify_image, my_login urlpatterns = [ # url(r'^login/$', login, name='login'), url(r'^login/$', my_login, name='login'), url(r'^logout/$', logout, name='logout'), url(r'^verify_image/(\d+)/(\d+)/$', verify_image, name='verify_image') # http://127.0.0.1:8000/account/verify_image/200/80/ ]
verify_image(request, width, height)函数进行获取my_login(request)进行验证,如果验证码验证通过且帐密验证通过则正常登录反跳转到next_url,否则,在登录界面将会进行验证错误提示。url(r'^id/(?P<event_pk>\d+)/$', ProcessView.as_view(), name='process'),
通过基于类的视图ProcessView(View)来查询显示该事件的处理过程
登录后跳转会事件页面
url(r'^start_processing/(?P<event_pk>\d+)/$', start_processing, name='start_processing'),
会访问start_processing(request, event_pk)视图更改事件的状态,后面可以直接通过这个视图回复处理过程。
点击开始处理,输入内容回复
其他用户也一起处理
事件处理完成后,可以点击处理完成,在这个阶段可以选择提交事件分析,或者是确认处理完成(这个阶段后就无法提交事件分析了)
url(r'^id/(?P<event_pk>\d+)/processed/$', processed, name='processed'),
通过processed(request, event_pk)修改事件处理结束状态。
url(r'^id/(?P<event_pk>\d+)/analysis/$', submit_analysis, name='submit_analysis'),
通过submit_analysis(request, event_pk)视图POST提交事件分析。
url(r'^id/(?P<event_pk>\d+)/processed/confirm/$', confirm_processed, name='confirm_processed'),
通过confirm_processed(request, event_pk)视图修改确认完成状态。
上方也会显示那些人处理过多少次
返回事件列表,进行筛选,可以筛选到这个刚确认完成的事件
筛选刚才完成的事件
然后点进去,将事件关闭
url(r'^id/(?P<event_pk>\d+)/close/$', event_close, name='event_close'),
通过event_close(request, event_pk)修改事件关闭状态。
在模板证需要根据事件的status值判断处于哪个状态,然后显示对应的视图。
后台文章管理
前端文章查看
class Tag(models.Model): name = models.CharField(max_length=64, verbose_name='标签名称') created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_time = models.DateTimeField(auto_now=True, verbose_name='修改时间') def __str__(self): return self.name class Meta: ordering = ['name', ] verbose_name = '标签名称' # 后台显示模型名称 verbose_name_plural = '标签列表'
class TagView(View): def get(self, request): current_url = reverse('wiki:tags') wiki_backend_url_list = get_group_url_list(current_url) tags = Tag.objects.all() return render(request, 'backend/wiki-tag.html', { 'tags': tags, 'WIKI_MENU_GROUP': wiki_backend_url_list, })
使用模态框进行标签的更新
<a class="btn btn-primary btn-circle" data-toggle="modal" onclick="edit_tag(this, {{ tag.id }}, '{{ tag.name }}')" title="修改标签"><i class="fa fa-list"></i></a> <form method="post" action="{% url 'wiki:update_tag' %}"> <div class="modal inmodal fade in" id="EditTag" tabindex="-1" role="dialog" aria-hidden="true" style="top: 120px;"> <div class="modal-dialog modal-sm"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> <h4 class="modal-title">更新标签名称</h4> <small class="font-bold"></small> </div> <div class="modal-body"> <input type="hidden" name="tag_id" id="tag_id" /> <div class="form-group"> <label>名称</label> <input type="text" name="tag_name" id="tag_name" required placeholder="标签旧名称" readonly class="form-control"> </div> <div class="form-group"> <label>更新名称</label> <input type="text" name="tag_new_name" id="tag_new_name" required placeholder="标签新名称" class="form-control"> </div> {% csrf_token %} </div> <div class="modal-footer"> <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button> <button type="submit" class="btn btn-primary">保存</button> </div> </div> </div> </div> </form> <!--模态框更新值--> <script> function edit_tag(obj, tag_id, tag_name) { $('#tag_id').val(tag_id); $('#tag_name').val(tag_name); // 最后显示出模态框 $('#EditTag').modal('show'); } </script>
def update_tag(request): tag_id = request.POST.get('tag_id') tag_name = request.POST.get('tag_name') tag_new_name = request.POST.get('tag_new_name') print(tag_id, tag_name, tag_new_name) tag = Tag.objects.get(id=tag_id, name=tag_name) tag.name = tag_new_name tag.save() return redirect(reverse('wiki:tags'))
<div class="col-xs-8 text-right"> <span> 添加标签 </span><span style="color: #000000; font-size: small" id="js_add_tag_error"></span> <h2 class="font-bold"> <form id="js_add_tag_form"> <div class="input-group"> <input type="text" name="name" class="form-control" placeholder="标签名称"> <span class="input-group-btn"> <button type="button" class="btn btn-primary" id="js_add_tag_btn">添加</button> </span> {% csrf_token %} </div> </form> </h2> </div> <script> $(function () { //监听这个button,用户如果点击了button。我们来向这个url进行post请求。 // 将我们的表单进行序列化。 $("#js_add_tag_btn").on('click', function () { $.ajax({ cache: false, type: 'POST', url: "{% url 'wiki:add_tag' %}", data: $('#js_add_tag_form').serialize(), async: true, success: function (data) { if(data.valid_status === "success"){ $("#js_add_tag_form")[0].reset(); $("#js_add_tag_error").html(''); refresh(); //刷新当前页面 }else if(data.valid_status === "fail"){ $("#js_add_tag_error").html(data.msg); } }, }); }); }) </script>
def add_tag(request): if request.method == "POST": name = request.POST.get('name', '') print(name) if len(name) == 0: return HttpResponse('{"valid_status": "fail", "msg": "名称不能为空"}', content_type='application/json') elif len(name) > 10: return HttpResponse('{"valid_status": "fail", "msg": "名称不能超过5字"}', content_type='application/json') else: if not Tag.objects.filter(name=name): Tag.objects.create(name=name) return HttpResponse('{"valid_status": "success"}', content_type='application/json') return HttpResponse('{"valid_status": "fail", "msg": "标签已存在"}', content_type='application/json')
<a class="btn btn-warning btn-circle" type="button" href="{% url 'wiki:del_tag' tag.id %}" onclick="delete_confirmation()" title="删除标签"><i class="fa fa-times"></i></a> <script> function delete_confirmation() { if (!confirm("确认删除?")) { window.event.returnValue = false; } } </script>
def del_tag(request, tag_id): tag = Tag.objects.filter(id=tag_id).delete() return redirect(reverse('wiki:tags'))
from DjangoMyStarMeow.permission import permission_forbidden # 403表示只能超级管理员,401表示必须登录 url(r'^tags/$', permission_forbidden(http_exception=403)(TagView.as_view()), name='tags'), url(r'^add_tag/$', add_tag, name='add_tag'), url(r'^del_tag/(?P<tag_id>\d+)/$', del_tag, name='del_tag'), url(r'^update_tag/$', update_tag, name='update_tag'),
用户管理用户页面的主题,用户可点击相应的名称来设置主题
标题以*开头的将关闭删除功能
支持关键字搜索,使用redis记录历史搜索和热搜
主题切换,使用js使用实时显示当前效果,用户登录可保存在数据库
点击logo即可悬浮显示导航,方便快速找到相应的文章
相当于后台管理内容部分的全屏版,另外一些删除,更新权限也有限制